﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.ComponentModel;

namespace Notoric.Model.Packaging
{
    /// <summary>
    /// Provides a base class for parts stored in a <see cref="Package"/>.
    /// This class is abstract.
    /// </summary>
    public abstract class PackagePart : INotifyPropertyChanged
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="PackagePart"/> instance.
        /// </summary>
        /// <param name="package">
        /// <para>
        /// Type: <see cref="Package"/>
        /// </para>
        /// <para>
        /// The parent package (the package that owns this part).
        /// </para>
        /// </param>
        /// <param name="implementationPart">
        /// <para>
        /// Type: <see cref="System.IO.Packaging.PackagePart"/>
        /// </para>
        /// <para>
        /// An existing part of the parent's <see cref="Notoric.Model.Packaging.Package.ImplementationPackage"/> that implements
        /// the core functionality of this part.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// Any of the parameters is <see langword="null"/>.
        /// </exception>
        protected PackagePart(Package package, System.IO.Packaging.PackagePart implementationPart)
            : this(package, null, implementationPart)
        {
        }

        /// <summary>
        /// Creates a new <see cref="PackagePart"/> instance.
        /// </summary>
        /// <param name="package">
        /// <para>
        /// Type: <see cref="Package"/>
        /// </para>
        /// <para>
        /// The parent package (the package that owns this part).
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="PackagePart"/>
        /// </para>
        /// <para>
        /// A package part that is the logical owner of this part (may be <see langword="null"/>).
        /// </para>
        /// </param>
        /// <param name="implementationPart">
        /// <para>
        /// Type: <see cref="System.IO.Packaging.PackagePart"/>
        /// </para>
        /// <para>
        /// An existing part of the parent's <see cref="Notoric.Model.Packaging.Package.ImplementationPackage"/> that implements
        /// the core functionality of this part.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="package"/> or <paramref name="implementationPart"/> is <see langword="null"/>.
        /// </exception>
        protected PackagePart(Package package, PackagePart owner, System.IO.Packaging.PackagePart implementationPart)
        {
            if (package == null)
                throw new ArgumentNullException("package");
            if (implementationPart == null)
                throw new ArgumentNullException("implementationPart");

            this.package = package;
            this.implementationPart = implementationPart;
            this.owner = owner;
            this.compressionOption = implementationPart.CompressionOption;
            this.contentType = implementationPart.ContentType;
            this.uri = implementationPart.Uri;
        }

        #endregion

        #region INotifyPropertyChanged Members

        /// <summary>
        /// Occurs when the value of some property has changed.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        #region Event Handlers

        /// <summary>
        /// Occurs when the <see cref="PropertyChanged"/> event is sent from some child element.
        /// </summary>
        /// <remarks>
        /// <para>
        /// <b>Operation:</b><br/>
        /// The handler sets the <see cref="IsModified"/> property to <see langword="true"/>.
        /// </para>
        /// <para>
        /// This handler is not attached automatically. The child elements are responsible
        /// for attaching this handler to their <see cref="PropertyChanged"/> event.
        /// </para>
        /// </remarks>
        /// <param name="sender">
        /// <para>
        /// Type: <see cref="System.Object"/>
        /// </para>
        /// <para>
        /// The object that has sent the event
        /// </para>
        /// </param>
        /// <param name="e">
        /// <para>
        /// Type: <see cref="PropertyChangedEventArgs"/>
        /// </para>
        /// <para>
        /// Event data
        /// </para>
        /// </param>
        internal void Content_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            IsModified = true;
        }

        #endregion

        #region Protected Methods

        /// <summary>
        /// When overriden in a derived class, saves the content of the part into the content stream.
        /// </summary>
        protected abstract void FlushCore();

        /// <summary>
        /// Raises the <see cref="PropertyChanged"/> event on this <see cref="PackagePart"/> instance.
        /// </summary>
        /// <param name="propertyName">
        /// <para>
        /// Type: <see cref="System.String"/>
        /// </para>
        /// <para>
        /// The name of the property whose value has changed.
        /// </para>
        /// </param>
        protected void RaisePropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        #endregion

        #region Internal Methods

        /// <summary>
        /// Replaces the implementation package part.
        /// This method is uniquely intended to allow restoring deleted project parts.
        /// </summary>
        /// <param name="implementationPart">
        /// <para>
        /// Type: <see cref="System.IO.Packaging.PackagePart"/>
        /// </para>
        /// <para>
        /// The new implementation package part.
        /// </para>
        /// </param>
        internal void ReplaceImplementationPart(System.IO.Packaging.PackagePart implementationPart)
        {
            this.implementationPart = implementationPart;
        }

        #endregion

        #region Internal Properties

        /// <summary>
        /// Gets the <see cref="System.IO.Packaging.PackagePart"/> instance that implements
        /// this package part.
        /// </summary>
        internal System.IO.Packaging.PackagePart ImplementationPart
        {
            get { return implementationPart; }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Saves the content of the part to the physical storage.
        /// </summary>
        /// <remarks>
        /// <para>
        /// <see cref="Flush"/> internally calls the derived class's <see cref="FlushCore"/> to perform the actual data storage.
        /// The derived class's <see cref="FlushCore"/> method is responsible for actually writing the part data into the part content stream.
        /// </para>
        /// <para>
        /// <see cref="Flush"/> does nothing if the <see cref="IsModified"/> property is <see langword="false"/>.
        /// </para>
        /// </remarks>
        public void Flush()
        {
            FlushCore();
            IsModified = false;
        }

        /// <summary>
        /// Returns the part content data stream.
        /// </summary>
        /// <remarks>
        /// This method opens the content stream with <see cref="FileMode"/> of <see cref="FileMode.OpenOrCreate"/>
        /// and <see cref="FileAccess"/> permissions that are the same as the parent <see cref="Package"/>.
        /// </remarks>
        /// <returns>
        /// <para>
        /// Type: <see cref="Stream"/>
        /// </para>
        /// <para>
        /// The content data stream for the part.
        /// </para>
        /// </returns>
        /// <exception cref="InvalidOperationException">
        /// The part has been deleted.<br/>
        /// -or-<br/>
        /// The <see cref="Package"/> is not open (<see cref="M:Package.Dispose()"/> or <see cref="M:Package.Close()"/> has been called).
        /// </exception>
        public Stream GetStream()
        {
            return implementationPart.GetStream();
        }

        /// <summary>
        /// Returns the content stream opened in a specified I/O <see cref="FileMode"/>.
        /// </summary>
        /// <remarks>
        /// The <see cref="FileAccess"/> permissions of the returned content stream are the same
        /// as the permissions of the parent <see cref="Package"/>.
        /// </remarks>
        /// <param name="mode">
        /// <para>
        /// Type: <see cref="FileMode"/>
        /// </para>
        /// <para>
        /// The I/O mode in which to open the content stream.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="Stream"/>
        /// </para>
        /// <para>
        /// The content data stream for the part.
        /// </para>
        /// </returns>
        /// <exception cref="InvalidOperationException">
        /// The part has been deleted.<br/>
        /// -or-<br/>
        /// The <see cref="Package"/> is not open (<see cref="Notoric.Model.Packaging.Package.Dispose()"/> or <see cref="Notoric.Model.Packaging.Package.Close"/> has been called).
        /// </exception>
        /// <exception cref="ArgumentOutOfRangeException">
        /// The <paramref name="mode"/> parameter is not a valid <see cref="FileMode"/> enumeration value.
        /// </exception>
        /// <exception cref="IOException">
        /// The <paramref name="mode"/> parameter is not compatible with the package and part stream.
        /// </exception>
        public Stream GetStream(FileMode mode)
        {
            return implementationPart.GetStream(mode);
        }

        /// <summary>
        /// Returns the part content stream opened with a specified <see cref="FileMode"/> and <see cref="FileAccess"/>.
        /// </summary>
        /// <param name="mode">
        /// <para>
        /// Type: <see cref="FileMode"/>
        /// </para>
        /// <para>
        /// The I/O mode in which to open the content stream.
        /// </para>
        /// </param>
        /// <param name="access">
        /// <para>
        /// Type: <see cref="FileAccess"/>
        /// </para>
        /// <para>
        /// The access permissions to use in opening the content stream.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="Stream"/>
        /// </para>
        /// <para>
        /// The content data stream for the part.
        /// </para>
        /// </returns>
        /// <exception cref="InvalidOperationException">
        /// The part has been deleted.<br/>
        /// -or-<br/>
        /// The <see cref="Package"/> is not open (<see cref="Notoric.Model.Packaging.Package.Dispose"/> or <see cref="Notoric.Model.Packaging.Package.Close"/> has been called).
        /// </exception>
        /// <exception cref="ArgumentOutOfRangeException">
        /// The <paramref name="mode"/> parameter is not a valid <see cref="FileMode"/> enumeration value.<br/>
        /// -or-<br/>
        /// The <paramref name="access"/> parameter is not a valid <see cref="FileAccess"/> enumeration value.
        /// </exception>
        /// <exception cref="IOException">
        /// <paramref name="mode"/> or <paramref name="access"/> is not compatible with the package and part stream.<br/>
        /// -or-<br/>
        /// The <paramref name="access"/> parameter is specified as <see cref="FileAccess.Read"/> but
        /// the <paramref name="mode"/> parameter requires write access.
        /// (<see cref="FileMode"/> values of <see cref="FileMode.Create"/>, <see cref="FileMode.CreateNew"/>, <see cref="FileMode.Truncate"/>,
        /// and <see cref="FileMode.Append"/> require <see cref="FileAccess.Write"/> or <see cref="FileAccess.ReadWrite"/> access.)
        /// </exception>
        public Stream GetStream(FileMode mode, FileAccess access)
        {
            return implementationPart.GetStream(mode, access);
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets the compression option of the part content stream.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="System.IO.Packaging.CompressionOption"/>
        /// </para>
        /// <para>
        /// The compression option of the part content stream.
        /// </para>
        /// </value>
        public System.IO.Packaging.CompressionOption CompressionOption
        {
            get { return compressionOption; }
        }

        /// <summary>
        /// Gets the MIME type of the content stream.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="string"/>
        /// </para>
        /// <para>
        /// The MIME type of the content data stream for the part.
        /// </para>
        /// </value>
        public string ContentType
        {
            get { return contentType; }
        }

        /// <summary>
        /// Gets the value indicating whether this part has unsaved changes or not.
        /// </summary>
        /// <remarks>
        /// <para>
        /// This flag is automatically set when the <see cref="PropertyChanged"/> event occurs in some child element.
        /// It is automatically cleared when <see cref="Flush"/> is called.
        /// </para>
        /// <para>
        /// The <see cref="Flush"/> method does nothing if <see cref="IsModified"/> is <see langword="false"/>.
        /// </para>
        /// </remarks>
        /// <value>
        /// <para>
        /// Type: <see cref="System.Boolean"/>
        /// </para>
        /// <para>
        /// Indicates whether this part has unsaved changes.
        /// </para>
        /// </value>
        public bool IsModified
        {
            get { return isModified; }
            set 
            {
                isModified = value;
                RaisePropertyChanged("IsModified");
            }
        }

        /// <summary>
        /// Gets the <see cref="PackagePart"/> that is the logical owner of this one.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="PackagePart"/>
        /// </para>
        /// <para>
        /// The <see cref="PackagePart"/> that logically owns this one (i.e. there is a relationship
        /// from the <see cref="Owner"/> part to this part with an ownership semantics).<br/>
        /// This value can be <see langword="null"/> if the part has not logical owner.
        /// </para>
        /// </value>
        public PackagePart Owner
        {
            get { return owner; }
        }

        /// <summary>
        /// Gets the parent <see cref="Package"/> of the part.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Package"/>
        /// </para>
        /// <para>
        /// The parent package of the part.
        /// </para>
        /// </value>
        public Package Package
        {
            get { return package; }
        }

        /// <summary>
        /// Gets the URI of the part.
        /// </summary>
        /// <remarks>
        /// The <see cref="Uri"/> property is set automatically by the <see cref="PackagePart"/> constructor.
        /// After it is set, the <see cref="Uri"/> of the part cannot be changed.
        /// </remarks>
        /// <value>
        /// <para>
        /// Type: <see cref="Uri"/>
        /// </para>
        /// <para>
        /// The URI of the part relative to the package root.
        /// </para>
        /// </value>
        /// <exception cref="InvalidOperationException">
        /// The part has been deleted.<br/>
        /// -or-<br/>
        /// The <see cref="Package"/> is not open (<see cref="Notoric.Model.Packaging.Package.Dispose"/> or <see cref="Notoric.Model.Packaging.Package.Close"/> has been called).
        /// </exception>
        public Uri Uri
        {
            get { return uri; }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the compression option for the part content stream.
        /// </summary>
        private System.IO.Packaging.CompressionOption compressionOption;
        /// <summary>
        /// Holds the MIME type of the part content stream.
        /// </summary>
        private string contentType;
        /// <summary>
        /// Holds the implementation part.
        /// </summary>
        private System.IO.Packaging.PackagePart implementationPart;
        /// <summary>
        /// Indicates whether this part has unsaved changes or not.
        /// </summary>
        private bool isModified;
        /// <summary>
        /// Holds a reference to the package part that is a logical owner of this one.
        /// </summary>
        private PackagePart owner;
        /// <summary>
        /// Holds a reference to the parent package.
        /// </summary>
        private Package package;
        /// <summary>
        /// Holds tha part URI.
        /// </summary>
        private Uri uri;

        #endregion
    }
}
